package icraf.carbon.core;

import java.util.HashMap;

public class Zone {
	public enum DataType {
		BELOWGROUND_EMISSION, 
		BG_EMISSION_FACTOR,
		MODIF_EMISSION,
		MODIF_EMISSION_FACTOR,
		
		// yang matrix LC x LC harus enum tersendiri! sama LC x Z
		CARBONSTOCK, COSTBENEFIT_CONVERSION, COSTBENEFIT_TOTAL, EMISSION, GENERAL, 
		// ini harus beda enum!
		LANDCOVER, LANDCOVER_CHANGE, LANDUSE_SCENARIO, LANDUSE_SCENARIO_B,  NPV, OPCOST, TPM_SCENARIO, TPM_SCENARIO_B, ZONE;

		public boolean equalsLabel(String s) {
			return s.equalsIgnoreCase(getLabel());
		}

		public String getLabel() {
			return "#" + this.toString();
		}
	}
	
	public static int EMISSION = 2, SEQUESTRATION = 1, NET_EMISSION = 0;
	public static int POSITIVE_NPV = 2, NEGATIVE_NPV = 1, NET_NPV = 0;

	private double areaRatio = 1;
	private HashMap<CostBenefitUnit, HashMap<LandCover, HashMap<LandCover, Double>>> costBenefitExtra = new HashMap<CostBenefitUnit, HashMap<LandCover, HashMap<LandCover, Double>>>();
	private HashMap<LandCover, Double> cStock = new HashMap<LandCover, Double>();
	private String description="";

	private boolean eligible = true;
	private String label;
	private HashMap<LandCover, HashMap<LandCover, Double>> landCoverChange,
			bgEmission, modifEmission, bgEmissionFactor, modifEmissionFactor, tpmScenario, tpmScenarioB, landuseScenario, landuseScenarioB;
	private HashMap<CostBenefitUnit, HashMap<LandCover, Double>> npv = new HashMap<CostBenefitUnit, HashMap<LandCover, Double>>();

	private Project project;

	public Zone() {
		super();
		init();
	}

	public Zone(String string) {
		label = string;
		init();
	}

	public double getAreaFraction() {
		return areaRatio;
	}

	public double getAreaSize() {
		return getProject().getTotalArea() * getAreaFraction();
	}

	public HashMap<LandCover, Double> getCarbonStockList() {
		return cStock;
	}

	/**
	 * a changes on NPV on defined land use, plus extra cost benefit for that
	 * land use change (in $/ha)
	 * 
	 * @param lc1
	 * @param lc2
	 * @param costBenefitUnit
	 * @return
	 */
	public double getCostBenefitTotal(LandCover lc1, LandCover lc2,
			CostBenefitUnit costBenefitUnit) {
		double lcChange = validate(getValue(lc1, lc2,
				DataType.LANDCOVER_CHANGE, null) * areaRatio);
		double cb = validate(getValue(lc1, lc2,
				DataType.COSTBENEFIT_CONVERSION, costBenefitUnit));
		double lc2_val = validate(getValue(lc2, DataType.NPV, costBenefitUnit));
		double lc1_val = validate(getValue(lc1, DataType.NPV, costBenefitUnit));
		return (lc2_val - lc1_val + cb) * lcChange;
	}

	private double validate(double d) {
		return Double.isNaN(d) ? 0 : d;
	}

	public String getDescription() {
		return description;
	}

	/**
	 * return carbon emission in t CO2/fraction-ha/year
	 * 
	 * @param lc1
	 *            from landcover
	 * @param lc2
	 *            to landcover
	 * @return
	 */
	public double getEmission(LandCover lc1, LandCover lc2) {
		// lcChange is in fraction
		double lcChange = validate(getValue(lc1, lc2,
				DataType.LANDCOVER_CHANGE, null) * areaRatio);
		double lc2_val = (cStock.get(lc2) == null) ? 0 : cStock.get(lc2);
		double lc1_val = (cStock.get(lc1) == null) ? 0 : cStock.get(lc1);
		// divided by duration time to get yearly average
		double e = (lc1_val - lc2_val) * lcChange * (44d / 12d)
				/ getProject().getDurationTime();
		if (getProject().isIncludeBelowgroundEmission()) {
			if(getProject().isUsingBelowgroundFactor()) {
				e += validate(getValue(lc1, lc2, DataType.BG_EMISSION_FACTOR,
						null)* lcChange);
			} else {
				e += validate(getValue(lc1, lc2, DataType.BELOWGROUND_EMISSION,
					null));
			}
		}
		if (getProject().isIncludeModifiedEmission()) {
			if(getProject().isUsingModifiedFactor()) {
				e += validate(getValue(lc1, lc2, DataType.MODIF_EMISSION_FACTOR, null)*lcChange);
			} else {
				e += validate(getValue(lc1, lc2, DataType.MODIF_EMISSION, null));
			}
		}
		return e;
	}
	
	public double getEmissionTotal(int type, boolean isEligibleOnly) {
		double sum = 0;
		LandCoverList landCoverList = project.getLandCoverList();
		for (LandCover lc1 : landCoverList) {
			for (LandCover lc2 : landCoverList) {
				double d = getEmission(lc1, lc2);
				if (type == EMISSION && d > 0) {
					if (isEligibleOnly) {
						if (isEligible() && project.isEligible(lc1, lc2)) {
							sum += d;
						}
					} else {
						sum += d;
					}
				} else if (type == SEQUESTRATION && d < 0) {
					if (isEligibleOnly) {
						if (isEligible() && project.isEligible(lc1, lc2)) {
							sum += d;
						}
					} else {
						sum += d;
					}
				} else if (type == NET_EMISSION) {
					if (isEligibleOnly) {
						if (isEligible() && project.isEligible(lc1, lc2)) {
							sum += d;
						}
					} else {
						sum += d;
					}
				}
			}
		}
		return sum;
	}
	
	public double getDeltaNPVTotal(int type, CostBenefitUnit costBenefitUnit, boolean isEligibleOnly) {
		double sum = 0;
		LandCoverList landCoverList = project.getLandCoverList();
		for (LandCover lc1 : landCoverList) {
			for (LandCover lc2 : landCoverList) {
				double d = getCostBenefitTotal(lc1, lc2, costBenefitUnit);
				if (type == POSITIVE_NPV && d > 0) {
					if (isEligibleOnly) {
						if (isEligible() && project.isEligible(lc1, lc2)) {
							sum += d;
						}
					} else {
						sum += d;
					}
				} else if (type == NEGATIVE_NPV && d < 0) {
					if (isEligibleOnly) {
						if (isEligible() && project.isEligible(lc1, lc2)) {
							sum += d;
						}
					} else {
						sum += d;
					}
				} else if (type == NET_NPV) {
					if (isEligibleOnly) {
						if (isEligible() && project.isEligible(lc1, lc2)) {
							sum += d;
						}
					} else {
						sum += d;
					}
				}
			}
		}
		return sum;
	}

	public Details getDetail(LandCover lc1, LandCover lc2, DataType t,
			Object attributes) {
		Details d = null;
		switch (t) {
		case EMISSION:
			d = getDetailEmission(lc1, lc2);
			break;
		case COSTBENEFIT_TOTAL:
			d = getDetailCBTotal(lc1, lc2, (CostBenefitUnit) attributes);
			break;
		case OPCOST:
			d = getDetailOpcost(lc1, lc2, (CostBenefitUnit) attributes);
			break;
		}
		if (d != null)
			d.setLandCover(lc1, lc2);
		return d;
	}

	private Details getDetailEmission(LandCover lc1, LandCover lc2) {
		double lc_ratio = (float) getValue(lc1, lc2, DataType.LANDCOVER_CHANGE,
				null);
		double area = (float) (lc_ratio * areaRatio);
		if ((cStock.get(lc1) == null) || (cStock.get(lc2) == null))
			return null;
		double c_change = (float) (cStock.get(lc1) - cStock.get(lc2));
		double c_change_tot = c_change * area;
		double emission = (float) (getEmission(lc1, lc2));

		Details infoArr = new Details();
		infoArr.add(new Info("Zone Area of " + getLabel() + " (fraction)",
				(float) areaRatio, "[a]"));
		infoArr.add(new Info("Land Use Transition (fraction)", lc_ratio, "[b]"));
		infoArr.add(new Info("Total Transition (fraction)", area, "[c=a*b]"));
		infoArr.add(new Info("C-Stock " + lc1.getLabel() + " (Mg/ha)", cStock
				.get(lc1), "[d]"));
		infoArr.add(new Info("C-Stock " + lc2.getLabel() + " (Mg/ha)", cStock
				.get(lc2), "[e]"));
		infoArr.add(new Info("C-Stock Changes (Mg/ha)", c_change, "[f=d-e]"));
		infoArr.add(new Info("C-Stock * Area (Mg/ha)", c_change_tot, "[g=f*c]"));
		infoArr.add(new Info("Duration Time (year)", getProject()
				.getDurationTime(), "[h]"));
		infoArr.add(new Info("CO2-eq ratio (44/12)", 44 / 12d, "[i]"));
		
		double bg_e = 0, m_e = 0;
		if (getProject().isIncludeBelowgroundEmission()) {
			if(getProject().isUsingBelowgroundFactor()) {
				bg_e = validate(getValue(lc1, lc2, DataType.BG_EMISSION_FACTOR,
						null)* area);
			} else {
				bg_e = validate(getValue(lc1, lc2, DataType.BELOWGROUND_EMISSION,
					null));
			}
		}
		if (getProject().isIncludeModifiedEmission()) {
			if(getProject().isUsingModifiedFactor()) {
				m_e = validate(getValue(lc1, lc2, DataType.MODIF_EMISSION_FACTOR, null)*area);
			} else {
				m_e = validate(getValue(lc1, lc2, DataType.MODIF_EMISSION, null));
			}
		}
		
		infoArr.add(new Info("Belowground Emission (Mg CO2-eq/ha.year)",bg_e,"[j]"));
		infoArr.add(new Info("Modified Emission (Mg CO2-eq/ha.year)",m_e, "[k]"));
		
		infoArr.add(new Info("Emission Average(Mg CO2-eq/ha.year)", emission,
				"[l=k+j+g*i/h]"));
		double a = getProject().getTotalArea();
		infoArr.add(new Info("Zone Area (ha)", a, "[m]"));
		infoArr.add(new Info("Emission Total per-Year (Mg CO2-eq/year)",
				emission * a, "[n=l*m]"));
		infoArr.setHighlightRows(infoArr.size() - 1, infoArr.size() - 3);
		return infoArr;
	}

	private Details getDetailCBTotal(LandCover lc1, LandCover lc2,
			CostBenefitUnit costBenefitUnit) {
		double lc_ratio = (float) getValue(lc1, lc2, DataType.LANDCOVER_CHANGE,
				null);
		double area = lc_ratio * areaRatio;
		if ((cStock.get(lc1) == null) || (cStock.get(lc2) == null))
			return null;
		double cb = getValue(lc1, lc2, DataType.COSTBENEFIT_CONVERSION,
				costBenefitUnit);
		double lc2_val = validate(getValue(lc2, DataType.NPV, costBenefitUnit));
		double lc1_val = validate(getValue(lc1, DataType.NPV, costBenefitUnit));
		double npv_changes = lc2_val - lc1_val;
		double cb_tot = (npv_changes + cb) * area;

		Details infoArr = new Details();
		infoArr.add(new Info("Zone Area of " + getLabel() + " (fraction)",
				areaRatio, "[a]"));
		infoArr.add(new Info("Land Use Transition (fraction)", lc_ratio, "[b]"));
		infoArr.add(new Info("Total Transition (fraction)", area, "[c=a*b]"));
		infoArr.add(new Info("NPV " + lc1.getLabel() + " ($/ha)", lc1_val,
				"[d]"));
		infoArr.add(new Info("NPV " + lc2.getLabel() + " ($/ha)", lc2_val,
				"[e]"));
		infoArr.add(new Info("NPV Changes ($/ha)", npv_changes, "[f=e-d]"));
		infoArr.add(new Info("Conversion Cost-Benefit ($/ha)", cb, "[g]"));

		infoArr.add(new Info("Cost-Benefit Total per-Ha ($/ha)", cb_tot,
				"[h=(f+g)*c]"));
		double a = getProject().getTotalArea();
		infoArr.add(new Info("Area Size (ha)", a, "[i]"));
		infoArr.add(new Info("Cost-Benefit Total ($)", cb_tot * a, "[j=h*i]"));
		infoArr.setHighlightRows(infoArr.size() - 1, infoArr.size() - 3);
		return infoArr;
	}

	private Details getDetailOpcost(LandCover lc1, LandCover lc2,
			CostBenefitUnit costBenefitUnit) {
		double t = getProject().getDurationTime();
		if ((cStock.get(lc1) == null) || (cStock.get(lc2) == null))
			return null;
		double cb = getValue(lc1, lc2, DataType.COSTBENEFIT_CONVERSION,
				costBenefitUnit);
		double lc2_val = validate(getValue(lc2, DataType.NPV, costBenefitUnit));
		double lc1_val = validate(getValue(lc1, DataType.NPV, costBenefitUnit));
		double npv_changes = lc2_val - lc1_val;
		double cb_tot = (npv_changes + cb);

		double c_change = cStock.get(lc1) - cStock.get(lc2);
		// double c_change_tot = c_change * area;
		double emission_tot = c_change * (44 / 12d);

		Details infoArr = new Details();
		infoArr.add(new Info("NPV " + lc1.getLabel() + " ($/ha)", lc1_val,
				"[a]"));
		infoArr.add(new Info("NPV " + lc2.getLabel() + " ($/ha)", lc2_val,
				"[b]"));
		infoArr.add(new Info("NPV Changes ($/ha)", npv_changes, "[c=b-a]"));
		infoArr.add(new Info("Conversion Cost-Benefit ($/ha)", cb, "[d]"));
		infoArr.add(new Info("Total Cost-Benefit ($/ha)", cb_tot, "[e=c+d]"));

		infoArr.add(new Info("C-Stock " + lc1.getLabel() + " (Mg/ha)", cStock
				.get(lc1), "[f]"));
		infoArr.add(new Info("C-Stock " + lc2.getLabel() + " (Mg/ha)", cStock
				.get(lc2), "[g]"));
		infoArr.add(new Info("C-Stock Changes (Mg/ha)", c_change, "[h=f-g]"));

		infoArr.add(new Info("CO2-eq ratio (44/12)", 44 / 12d, "[i]"));
		infoArr.add(new Info("Belowground Emission (Mg CO2-eq/ha)",
				(getProject().isIncludeBelowgroundEmission() ? getValue(lc1,
						lc2, DataType.BELOWGROUND_EMISSION, null) * t
						: Double.NaN), "[j]"));
		infoArr.add(new Info("Modified Emission (Mg CO2-eq/ha)", (getProject()
				.isIncludeModifiedEmission() ? getValue(lc1, lc2,
				DataType.MODIF_EMISSION, null) * t : Double.NaN), "[k]"));
		infoArr.add(new Info("Total Emission (Mg CO2-eq/ha)", emission_tot,
				"[l=k+j+i*h]"));
		infoArr.add(new Info("Opportunity Cost ($/Mg CO2-eq)", cb_tot
				/ emission_tot, "[m=e/l]"));

		infoArr.setHighlightRows(infoArr.size() - 1);
		return infoArr;
	}

	public String getLabel() {
		return label;
	}

	// private double getNPV(LandCover lc, CostBenefitUnit costBenefitUnit) {
	// // HashMap<LandCover, Double> n = npv.get(costBenefitUnit);
	// // if (n == null)
	// // return Double.NaN;
	// // Double d = n.get(lc);
	// // return d==null?Double.NaN:d;
	// return getValue(lc, DataType.NPV, costBenefitUnit);
	// }

	public HashMap<LandCover, Double> getNPVList(CostBenefitUnit costBenefitUnit) {
		HashMap<LandCover, Double> n = npv.get(costBenefitUnit);
		if (n == null) {
			n = new HashMap<LandCover, Double>();
			npv.put(costBenefitUnit, n);
		}
		return n;
	}

	// $/tCO2
	public double getOpportunityCost(LandCover lc1, LandCover lc2,
			CostBenefitUnit costBenefitUnit) {
		double e = getEmission(lc1, lc2);
		if (e == 0)
			return 0;
		double n = getCostBenefitTotal(lc1, lc2, costBenefitUnit)
				/ getProject().getDurationTime();
		return n / e;
	}

	public Project getProject() {
		return project;
	}

	public double getValue(LandCover lc, DataType t, Object attributes) {
		HashMap<LandCover, Double> data = null;
		switch (t) {
		case NPV:
			if (attributes != null && attributes instanceof CostBenefitUnit)
				data = npv.get(attributes);
			break;
		case CARBONSTOCK:
			data = cStock;
			break;
		}
		if (data == null)
			return Double.NaN;
		Double d = data.get(lc);
		return d == null ? 0 : d;
	}

	/**
	 * return value of a matrix element of datatype t on row lc1 an column lc2
	 * 
	 * @param lc1
	 *            row header landcover
	 * @param lc2
	 *            column header landcover
	 * @param t
	 *            data type
	 * @param attributes
	 *            additional parameters of datatype
	 * @return
	 */
	public double getValue(LandCover lc1, LandCover lc2, DataType t,
			Object attributes) {
		HashMap<LandCover, HashMap<LandCover, Double>> data = null;
		switch (t) {
		case EMISSION:
			return getEmission(lc1, lc2);
		case LANDCOVER_CHANGE:
			data = landCoverChange;
			break;
		case BELOWGROUND_EMISSION:
			data = bgEmission;
			break;
		case MODIF_EMISSION:
			data = modifEmission;
			break;
		case BG_EMISSION_FACTOR:
			data = bgEmissionFactor;
			break;
		case MODIF_EMISSION_FACTOR:
			data = modifEmissionFactor;
			break;	
		case COSTBENEFIT_CONVERSION:
			if (attributes != null && attributes instanceof CostBenefitUnit)
				data = costBenefitExtra.get(attributes);
			break;
		case COSTBENEFIT_TOTAL:
			if (attributes != null && attributes instanceof CostBenefitUnit)
				return getCostBenefitTotal(lc1, lc2,
						(CostBenefitUnit) attributes);
		case OPCOST:
			if (attributes != null && attributes instanceof CostBenefitUnit)
				return getOpportunityCost(lc1, lc2,
						(CostBenefitUnit) attributes);
		case TPM_SCENARIO:
			data = tpmScenario;
			break;
		case TPM_SCENARIO_B:
			data = tpmScenarioB;
			break;
		case LANDUSE_SCENARIO:
			data = landuseScenario;
			break;
		case LANDUSE_SCENARIO_B:
			data = landuseScenarioB;
			break;
		}
		if (data == null)
			return Double.NaN;
		if ((lc1 == null) || (lc2 == null)) {
			return Double.NaN;
		}
		HashMap<LandCover, Double> lc1List = data.get(lc1);

		if (lc1List == null)
			return 0;
		if (lc1List.get(lc2) == null)
			return 0;
		double d = lc1List.get(lc2);
		return Double.isNaN(d)?0:d;
	}

	public double getValueTotal(DataType t, Object attributes) {
		double total = 0;
		LandCoverList landCoverList = project.getLandCoverList();
		for (LandCover lc1 : landCoverList) {
			for (LandCover lc2 : landCoverList) {
				total += getValue(lc1, lc2, t, attributes);
			}
		}
		return Double.isNaN(total)?0:total;
	}

	private void init() {
		landCoverChange = new HashMap<LandCover, HashMap<LandCover, Double>>();
		bgEmission = new HashMap<LandCover, HashMap<LandCover, Double>>();
		modifEmission = new HashMap<LandCover, HashMap<LandCover, Double>>();
		bgEmissionFactor = new HashMap<LandCover, HashMap<LandCover, Double>>();
		modifEmissionFactor = new HashMap<LandCover, HashMap<LandCover, Double>>();
		tpmScenario = new HashMap<LandCover, HashMap<LandCover, Double>>();
		landuseScenario = new HashMap<LandCover, HashMap<LandCover, Double>>();
		tpmScenarioB = new HashMap<LandCover, HashMap<LandCover, Double>>();
		landuseScenarioB = new HashMap<LandCover, HashMap<LandCover, Double>>();
	}

	public boolean isEligible() {
		return eligible;
	}

	public double normalizeValue(DataType t, Object attributes) {
		double total = getValueTotal(t, attributes);
		LandCoverList landCoverList = project.getLandCoverList();
		for (LandCover lc1 : landCoverList) {
			for (LandCover lc2 : landCoverList) {
				double v = getValue(lc1, lc2, t, attributes) / total;
				v = Double.isNaN(v) ? 0 : v;
				setValue(v, lc1, lc2, t, attributes);
			}
		}
		return total;
	}

	public void removeLandCover(LandCover lc) {
		removeLCMap(landCoverChange, lc);
		removeLCMap(bgEmission, lc);
		removeLCMap(modifEmission, lc);
		removeLCMap(bgEmissionFactor, lc);
		removeLCMap(modifEmissionFactor, lc);
		removeLCMap(tpmScenario, lc);
		removeLCMap(landuseScenario, lc);
		removeLCMap(tpmScenarioB, lc);
		removeLCMap(landuseScenarioB, lc);

		cStock.remove(lc);
		for (CostBenefitUnit c : project.getCostBenefitUnitList()) {
			removeLCMap(costBenefitExtra.get(c), lc);
			if (npv.get(c) != null)
				npv.get(c).remove(lc);
		}
	}

	private void removeLCMap(
			HashMap<LandCover, HashMap<LandCover, Double>> map, LandCover lc) {
		if (map == null)
			return;
		map.remove(lc);
		for (HashMap<LandCover, Double> list : map.values()) {
			list.remove(lc);
		}
	}

	public void setAreaRatio(double areaRatio) {
		this.areaRatio = areaRatio;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public void setEligible(boolean eligible) {
		this.eligible = eligible;
	}

	public void setLabel(String label) {
		this.label = label.trim();
	}

	// public void setNPV(double d, LandCover lc, CostBenefitUnit
	// costBenefitUnit) {
	// HashMap<LandCover, Double> n = npv.get(costBenefitUnit);
	// if (n == null)
	// n = new HashMap<LandCover, Double>();
	// n.put(lc, d);
	// npv.put(costBenefitUnit, n);
	// }

	public void setProject(Project project) {
		this.project = project;
	}

	public void setValue(double d, LandCover lc, DataType t, Object attributes) {
		HashMap<LandCover, Double> data = null;
		switch (t) {
		case NPV:
			if (attributes != null && attributes instanceof CostBenefitUnit)
				data = npv.get(attributes);
			if (data == null)
				data = new HashMap<LandCover, Double>();
			break;
		case CARBONSTOCK:
			data = cStock;
			break;
		}
		if (data == null)
			return;
		data.put(lc, d);
		if (t == DataType.NPV) {
			npv.put((CostBenefitUnit) attributes, data);
		}
	}

	public void setValue(double d, LandCover lc1, LandCover lc2, DataType t,
			Object attributes) {
		HashMap<LandCover, HashMap<LandCover, Double>> data = null;
		switch (t) {
		case COSTBENEFIT_CONVERSION:
			if (attributes != null && attributes instanceof CostBenefitUnit)
				data = costBenefitExtra.get(attributes);
			if (data == null)
				data = new HashMap<LandCover, HashMap<LandCover, Double>>();
			break;
		case LANDCOVER_CHANGE:
			data = landCoverChange;
			break;
		case BELOWGROUND_EMISSION:
			data = bgEmission;
			break;
		case MODIF_EMISSION:
			data = modifEmission;
			break;
		case BG_EMISSION_FACTOR:
			data = bgEmissionFactor;
			break;
		case MODIF_EMISSION_FACTOR:
			data = modifEmissionFactor;
			break;
		case TPM_SCENARIO:
			data = tpmScenario;
			break;
		case TPM_SCENARIO_B:
			data = tpmScenarioB;
			break;
		case LANDUSE_SCENARIO:
			data = landuseScenario;
			break;
		case LANDUSE_SCENARIO_B:
			data = landuseScenarioB;
			break;
		}
		if (data == null)
			return;
		HashMap<LandCover, Double> lc1List = data.get(lc1);
		if (lc1List == null) {
			lc1List = new HashMap<LandCover, Double>();
		}
		lc1List.put(lc2, d);
		data.put(lc1, lc1List);
		if (t == DataType.COSTBENEFIT_CONVERSION) {
			costBenefitExtra.put((CostBenefitUnit) attributes, data);
		}
	}

	@Override
	public String toString() {
		return getLabel();
	}

}
